// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Author: Markus Gutschke

#ifdef USE_GNUTLS

#include <errno.h>
#include <stdlib.h>

#include "check.h"
#include "ssl.h"


struct SSL *newSSL(void) {
  struct SSL *ssl;
  check(ssl = malloc(sizeof(struct SSL)));
  initSSL(ssl);
  return ssl;
}

void initSSL(struct SSL *ssl) {
  static int initialized = 0;
  if (!initialized) {
    // Global initialization
    ++initialized;
    check(gnutls_global_init() == GNUTLS_E_SUCCESS);
    atexit(gnutls_global_deinit);
  }
  check(gnutls_init(&ssl->session, GNUTLS_CLIENT) == GNUTLS_E_SUCCESS);
  check(gnutls_certificate_allocate_credentials(&ssl->cred)==GNUTLS_E_SUCCESS);
  check(gnutls_credentials_set(ssl->session, GNUTLS_CRD_CERTIFICATE,
                               ssl->cred) == GNUTLS_E_SUCCESS);
  check(gnutls_priority_set_direct(ssl->session,
                                   "PERFORMANCE:!ARCFOUR-128",
                                   NULL) == GNUTLS_E_SUCCESS);
}

void destroySSL(struct SSL *ssl) {
  if (ssl) {
    gnutls_deinit(ssl->session);
    gnutls_certificate_free_credentials(ssl->cred);
  }
}

void deleteSSL(struct SSL *ssl) {
  destroySSL(ssl);
  free(ssl);
}

struct SSLHandle *openSSLHandle(struct SSL *ssl, int fd) {
  gnutls_transport_set_ptr(ssl->session, (gnutls_transport_ptr_t)(intptr_t)fd);
  int rc;
  do {
    check((rc = gnutls_handshake(ssl->session))
              == GNUTLS_E_SUCCESS ||
           rc == GNUTLS_E_AGAIN   ||
           rc == GNUTLS_E_INTERRUPTED);
  } while (rc == GNUTLS_E_AGAIN   ||
           rc == GNUTLS_E_INTERRUPTED);
  return (struct SSLHandle *)&ssl->session;
}

void closeSSLHandle(struct SSLHandle *handle) {
  gnutls_session_t *session = (gnutls_session_t *)handle;
  gnutls_bye(*session, GNUTLS_SHUT_RDWR);
}

ssize_t SSLread(struct SSLHandle *handle, char *buf, size_t sz) {
  gnutls_session_t *session = (gnutls_session_t *)handle;
  int rc;
  do {
    rc = gnutls_record_recv(*session, buf, sz);
    if (rc == GNUTLS_E_REHANDSHAKE) {
      do {
        check((rc = gnutls_handshake(*session))
                  == GNUTLS_E_SUCCESS ||
               rc == GNUTLS_E_AGAIN   ||
               rc == GNUTLS_E_INTERRUPTED);
      } while (rc == GNUTLS_E_AGAIN   ||
               rc == GNUTLS_E_INTERRUPTED);
      continue;
    }
  } while (0);
  if (rc == GNUTLS_E_AGAIN) {
    errno = EAGAIN;
    return -1;
  } else if (rc == GNUTLS_E_INTERRUPTED) {
    errno = EINTR;
    return -1;
  } else if (rc < 0) {
    errno = EIO;
    return -1;
  }
  return rc;
}

ssize_t SSLwrite(struct SSLHandle *handle, const char *buf, size_t sz) {
  gnutls_session_t *session = (gnutls_session_t *)handle;
  int rc = gnutls_record_send(*session, buf, sz);
  if (rc == GNUTLS_E_AGAIN) {
    errno = EAGAIN;
    return -1;
  } else if (rc == GNUTLS_E_INTERRUPTED) {
    errno = EINTR;
    return -1;
  } else if (rc < 0) {
    errno = EIO;
    return -1;
  }
  return rc;
}

#endif  // USE_GNUTLS
